package monopoli.communication;

import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

import java.util.List;

/**
 *
 * @author Marco
 */
public class InitializationServer implements InitializationServerMethods {

    //Attributi pubblici statici
    public static final String SERVER_DEFAULT_IP = "130.136.4.134"; //oppure usare 127.0.0.1 per il localhost
    public static final int SERVER_DEFAULT_PORT = 2001;

    public static final int HOSTS_FAIL_LIMIT = 3;
    
    public static final int STATE_ANYTHING = 0;
    public static final int STATE_ACCEPTING = 1;
    public static final int STATE_ACCEPTING_FINISHED = 2;
    public static final int STATE_START_GAME = 3;
    
    public static final int MIN_PLAYERS = 3;
    public static final int MAX_PLAYERS = 6;
    public static final long ACCEPTING_TIME = 40000;   //3 minuti=180000
    
    //Attributi privati
    private static InitializationServer objInitServer;
    private static InitializationServerMethods stubInitServer;
    private static HostMethods objHost;
    private static Registry registryInitServer, registryHost;
    private static List<Player> pl;
    private static String serverIp;
    private static int server_port;
    private int state;
    private long msgCount;




    //Costruttore
    public InitializationServer() throws RemoteException {
        super();

        //sp = new SynchronizerPlayer(MIN_PLAYERS, MAX_PLAYERS);

        state = STATE_ACCEPTING;
        msgCount = 0;
        pl = Collections.synchronizedList(new ArrayList<Player>())  ;

    }



    //Metodi privati
    private boolean checkCall(Message msg, int msg_type, int state, String method){
        
        Message retMsg = null;
        
        //Controlla che il messaggio sia di tipo corretto
        if (msg.getType() != msg_type) {

            ArrayList<Object> al = new ArrayList<Object>();
            al.add(new String("Ricezione messaggio di tipologia non compatibile"));

            retMsg = new Message();

            retMsg.setId(msgCount);
            retMsg.setIdComplete(msgCount++ + "_InitServer");
            retMsg.setText("MonopoliServer: Method " + method +"()");
            retMsg.setType(Message.TYPE_ERROR);
            retMsg.setPayload(al);
            
            return false;
        }
        
        //Controlla che lo stato del server sia compatibile col metodo chiamato
        if (this.state != state && this.state != STATE_ANYTHING) {

            ArrayList<Object> al = new ArrayList<Object>();
            al.add(new String("Metodo non disponibile in questo stato del server"));

            retMsg = new Message();

            retMsg.setId(msgCount);
            retMsg.setIdComplete(msgCount++ + "_InitServer");
            retMsg.setText("MonopoliServer: Method " + method +"()");
            retMsg.setType(Message.TYPE_ERROR);
            retMsg.setPayload(al);
            
            return false;
        }
        
        //Controlla che il numero di giocatori sia sotto il limite
        if (pl.size() > MAX_PLAYERS) {

            ArrayList<Object> al = new ArrayList<Object>();
            al.add(new String("Metodo non disponibile perchè è stato raggiunto il numero massiomo di giocatori"));

            retMsg = new Message();

            retMsg.setId(msgCount);
            retMsg.setIdComplete(msgCount++ + "_InitServer");
            retMsg.setText("MonopoliServer: Method " + method +"()");
            retMsg.setType(Message.TYPE_ERROR);
            retMsg.setPayload(al);
            
            return false;
        }
        
        return true;
    }

    private static boolean connectToHost(RMIAddress rmiA) {

        String bindName = rmiA.getBindName();
        String hostIp = rmiA.getIp();
        int host_port = rmiA.getPort();
        
        try {

            registryHost = LocateRegistry.getRegistry(hostIp, host_port);
            objHost = (HostMethods) registryHost.lookup(bindName);
            
        } catch (NotBoundException nbe) {
            System.out.println("[DBG_com] NotBoundException: " + nbe.getMessage());
            return false;
        } catch (AccessException ae) {
            System.out.println("[DBG_com] AccessException: " + ae.getMessage());
            return false;
        } catch (RemoteException re) {
            System.out.println("[DBG_com] RemoteException: " + re.getMessage());
            return false;
        }

        return true;
    }
    
    private static boolean checkParameters(String[] args) {

        serverIp = InitializationServer.SERVER_DEFAULT_IP;
        server_port = InitializationServer.SERVER_DEFAULT_PORT;

        if (args.length == 0) {  }
        else if (args.length == 2) {
            serverIp = args[0];
            server_port = new Integer(args[1]).intValue();
        }
        else {
            System.out.println("[DBG_com] MonopoliServer: inserire nome dell'host e porta su cui avviare il server");
            return false;
        }

        return true;
    }

    private static boolean bindServer(String serverIp, int server_port) {
        try {
                objInitServer = new InitializationServer();  //potrebbe volerci this al posto di new InitializationServer()
                stubInitServer = (InitializationServerMethods) UnicastRemoteObject.exportObject(objInitServer, 0);
                registryInitServer = LocateRegistry.getRegistry(serverIp, server_port);

                registryInitServer.rebind("MonopoliServer", stubInitServer);


                System.out.println("[DBG_com] MonopoliServer: binded su IP=" + serverIp + " e porta=" + server_port + " con nome MonopoliServer");
                System.out.println("[DBG_com] MonopoliServer: ready con ThreadID=" + Thread.currentThread().getId());

            } catch (AccessException ae) {
                System.out.println("[DBG_com] MonopoliServer: AccessException " + ae.getMessage());
                return false;

            } catch (RemoteException re) {
                System.out.println("[DBG_com] MonopoliServer: RemoteException " + re.getMessage());
                return false;
            }

        return true;
    }

    private static void waitRequests() {


        long interval = ACCEPTING_TIME / 5;

        //Addormento l'InitializationServer per permettere di ricevere delle request()
        try {

            for(int i=1; i<=5; i++) {
                Thread.sleep(interval);
                System.out.println("[DBG_com] MonopoliServer: rimasti "
                        + (ACCEPTING_TIME - (i*interval))/1000
                        + "s alla fine dell'accettazione");
            }



        } catch (InterruptedException ie) {
            System.out.println("[DBG_com] MonopoliServer: attesa di request() interrotta da un interrupt");
            ie.printStackTrace();
        }
    }

    private static void responseToRequests() {

        System.out.println("[DBG_com] MonopoliServer: fine tempo di accettazione");

        //Caso numero minimo di giocatori non raggiunto
        if (pl.size() < InitializationServer.MIN_PLAYERS) {

            for (Iterator it = pl.iterator(); it.hasNext();) {
                Player p = (Player)it.next();

                //Aggiornamento deI metodi RMI all'host corrente
                connectToHost(p.getRMIA());
                
                try {
                    objHost.rifiutaPartecipaPartita();
                } catch (RemoteException re) {
                    System.out.println("[DBG_com] RemoteException per l'host con ip=" + p.getRMIA().getIp() + " e porta=" + p.getRMIA().getPort());
                }

            }
        }
        //Caso numero giocatori compreso nei limiti
        else if ((pl.size() >= InitializationServer.MIN_PLAYERS) && (pl.size() <= InitializationServer.MAX_PLAYERS)) {

            Player p = null;
            ArrayList<Player> nextPlayers = null;


            for (int i=0; i<pl.size(); i++) {

                nextPlayers = new ArrayList<Player>();

                //Ricavo del giocatore i-esimo
                p = pl.get(i);

                //Aggiornamento deI metodi RMI all'host corrente
                connectToHost(p.getRMIA());


                //Ricava gli RMIA per gli host successivi in base al limite di fail
                for(int j=1; j<=InitializationServer.HOSTS_FAIL_LIMIT+1; j++) {

                    if(i+j >= pl.size())
                        nextPlayers.add(pl.get(i+j-pl.size()));
                    else
                        nextPlayers.add(pl.get(i+j));
                }
                

                //Chiamata del metodo remoto
                try {
                    objHost.confermaPartecipaPartita(i+1, nextPlayers);
                } catch (RemoteException re) {
                    System.out.println("[DBG_com] RemoteException per l'host con ip=" + p.getRMIA().getIp() + " e porta=" + p.getRMIA().getPort() + re.getMessage());
                }
                
            }
        }
        //Caso numero giocatori maggiore del massimo possibile
        else {

            Player p = null;
            ArrayList<Player> nextPlayers  = new ArrayList<Player>();


            for (int i = 0; i < pl.size(); i++) {

                p = pl.get(i);

                //Aggiornamento deI metodi RMI all'host corrente
                connectToHost(p.getRMIA());


                if (i+1 <= InitializationServer.MAX_PLAYERS) {

                    for(int j=1; j<=InitializationServer.HOSTS_FAIL_LIMIT+1; j++) {

                        if(i+j >= pl.size())
                            nextPlayers.add(pl.get(i+j-pl.size()));
                        else
                            nextPlayers.add(pl.get(i+j));

                    }

                    //Chiamata del metodo remoto
                    try {
                        objHost.confermaPartecipaPartita(i+1, nextPlayers);
                    } catch (RemoteException re) {
                        System.out.println("[DBG_com] RemoteException per l'host con ip=" + p.getRMIA().getIp() + " e porta=" + p.getRMIA().getPort() + re.getMessage());
                    }
                }
                else {

                    //Chiamata del metodo remoto
                    try {
                        objHost.rifiutaPartecipaPartita();
                    } catch (RemoteException re) {
                        System.out.println("[DBG_com] RemoteException per l'host con ip=" + p.getRMIA().getIp() + " e porta=" + p.getRMIA().getPort() + re.getMessage());
                    }
                }
            }
        }

    }







    //Metodi RMI
    @Override
    public synchronized Message ping(Message msg) throws RemoteException {

        Message retMsg;

        //Controlla le condizioni di chaiamata del metodo
        checkCall(msg, Message.TYPE_PING, STATE_ANYTHING, "ping");

        //Recupero dati del messaggio
        String ipClient = (String)msg.getPayload().get(0);

        
        System.out.println("[DBG_com] Method ping(): client = " + ipClient);


        retMsg = new Message();
        retMsg.setId(msgCount);
        retMsg.setIdComplete(msgCount++ + "_InitServer");
        retMsg.setText("MonopoliServer: Method ping() sul ThreadID=" +
                        Thread.currentThread().getId());
        retMsg.setType(Message.TYPE_PING);
        retMsg.setPayload(null);

        return retMsg;
    }

    @Override
    public synchronized Message request(Message msg) throws RemoteException {

        //Controlla le condizioni di chaiamata del metodo
        checkCall(msg, Message.TYPE_REQUEST, STATE_ACCEPTING, "request");

        System.out.println("[DBG_com] Method request()");

        //Recupero dati del messaggio
        Integer idGiocatore  = new Integer((String)msg.getPayload().get(0));
        RMIAddress rmiA = (RMIAddress)msg.getPayload().get(1);


        //Aggiunta nuovo giocatore alla lista giocatori
        Player p = new Player();

        p.setIdGiocatore(idGiocatore.intValue());
        p.setRMIA(rmiA);

        pl.add(p);

        
        //Costruzione del messaggio di ritorno
        Message retMsg = new Message();

        retMsg.setId(msgCount);
        retMsg.setIdComplete(msgCount++ + "_InitServer");
        retMsg.setText("Method request() effettuata con successo");
        retMsg.setType(Message.TYPE_REQUEST);
        retMsg.setPayload(null);

        return retMsg;
    }





    

    
    //Main Method
    public static void main(String[] args) {

            boolean ret;
            

            //Controllo di invocazione corretta del programma
            ret = checkParameters(args);
            if (!ret) System.exit(1);
            
            //Binding del server
            ret = bindServer(serverIp, server_port);
            if (!ret) System.exit(1);
            
            //Attesa di chiamate di request()
            waitRequests();

            //Invio della conferma o rifiuto partita
            responseToRequests();

            //Fine server
            System.out.println("[DBG_com] MonopoliServer: fine impostazione hosts");
            System.exit(0);
    }
}

